package team.zero.lemon.module.workflow.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import team.zero.lemon.common.date.UTC;
import team.zero.lemon.common.exception.CustomException;
import team.zero.lemon.common.service.CrudService;
import team.zero.lemon.common.utils.BitComput;
import team.zero.lemon.common.utils.StreamUtils;
import team.zero.lemon.module.sys.news.service.NewsService;
import team.zero.lemon.module.workflow.dao.*;
import team.zero.lemon.module.workflow.entity.*;
import team.zero.lemon.module.workflow.entity.Enum.*;
import team.zero.lemon.module.workflow.entity.Parameter.Create;
import team.zero.lemon.module.workflow.entity.Parameter.Submit;
import team.zero.lemon.module.workflow.entity.Parameter.Participants;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Created by bolin on 2016/10/19.
 */
@Service
public class WorkflowService extends CrudService<IInstanceDao, Instance> {

    @Autowired
    private InstanceService instanceService;
    @Autowired
    private ActivityService activityService;
    @Autowired
    private WorkitemService workitemService;
    @Autowired
    private WorkitemHandlerService workitemHandlerService;
    @Autowired
    private ParticipantService participantService;
    @Autowired
    private NewsService newsService;

    @Transactional
    public Workitem create(Create create) {
        List<Activity> wfActivities = activityService.findStartActivityByTemplateId(create.getTemplateId());
        if (wfActivities.size() < 1) {
            throw new CustomException("没找到开始活动!");
        }
        Instance instance = new Instance();
        instance.setTemplateId(create.getTemplateId());
        instance.setInstanceName(create.getInstanceName());
        instance.setStartUserId(create.getParticipants().getUserId());
        instance.setStartUserName(create.getParticipants().getUserName());
        instance.setOrgId(create.getParticipants().getOrgId());
        instance.setOrgName(create.getParticipants().getOrgName());
        instance.setRunState(RunState.running.ordinal());
        instance.setStartTime(UTC.date());
        instance.setParentInstanceId(create.getParentInstanceId());
//        instance.setPathUrl();
        // TODO: 2016/11/16 setPathUrl
        int instanceId = save(instance);
        Workitem workitem = new Workitem();
        workitem.setInstanceId(instanceId);
        workitem.setActivityId(wfActivities.get(0).getId());
        workitem.setActivityName(wfActivities.get(0).getActivityName());
        workitem.setStartUserId(create.getParticipants().getUserId());
        workitem.setStartUserName(create.getParticipants().getUserName());
        workitem.setGenerateType(GenerateType.normal.ordinal());
        workitem.setHandleResult(HandleResult.none.ordinal());
        workitem.setStartTime(UTC.date());
        workitem.setClientType(create.getClient());
        int workitemId = workitemService.save(workitem);
        WorkitemHandler workitemHandler = new WorkitemHandler();
        workitemHandler.setWorkitemId(workitemId);
        workitemHandler.setUserId(create.getParticipants().getUserId());
        workitemHandler.setUserName(create.getParticipants().getUserName());
        workitemHandler.setHandleResult(HandleResult.none.ordinal());
        workitemHandler.setStartTime(UTC.date());
        workitemHandler.setClientType(create.getClient());
        workitemHandlerService.save(workitemHandler);
        return workitem;
    }

    @Transactional
    public boolean submit(Submit submit) {
        try {
            Instance instance = instanceService.findById(submit.getInstanceId());
            for (int ruleId : submit.getRuleIds()) {
                submit.setWorkItemId(Do(instance, submit.getWorkItemId(), ruleId, submit.getParticipants(), submit.getParticipantses(), HandleResult.values()[submit.getResult()], submit.getSuggestion(), submit.getClient()));
                instance.setInsert(false);
                instanceService.save(instance);
            }
            return true;
        } catch (Exception exc) {
            throw new CustomException(exc);
        }
    }

    /*
    处理工作流提交
     */
    @Transactional
    private int Do(Instance instance, int workitmeId, Integer ruleId, Participants participants, List<Participants> participantses, HandleResult handleResult, String handleSuggestion, Integer client) {
        try {
            Workitem currentWorkitem = workitemService.findById(workitmeId);
            currentWorkitem.setWorkitemHandlers(workitemHandlerService.findByWorkitemId(currentWorkitem.getId()));
            Activity currentActivity = activityService.findById(currentWorkitem.getActivityId());
            Activity nextActivity = activityService.findNextActivityByRuleId(ruleId);
            Workitem workitem = new Workitem();
            if (doWorkitem(currentWorkitem, currentActivity, participants, handleResult, handleSuggestion)) {
                instance.setRunState(RunState.running.ordinal());
                switch (ActivityType.values()[nextActivity.getActivityType()]) {
                    case drafting:
                        participantses = new ArrayList<>();
                        participantses.add(participants);
                        doParticipants(instance.getId(), nextActivity.getId(), participantses, handleResult);
                        workitem = generateWorkItem(instance.getId(), nextActivity.getId(), nextActivity.getActivityName(), currentWorkitem.getId(), participants, participantses, GenerateType.normal.ordinal(), new Long(BitComput.pro((long) nextActivity.getStatus(), ActivityFunction.ENABLE_SIGNATURE)).intValue(), client);
                        break;
                    case approval:
                        doParticipants(instance.getId(), nextActivity.getId(), participantses, handleResult);
                        workitem = generateWorkItem(instance.getId(), nextActivity.getId(), nextActivity.getActivityName(), currentWorkitem.getId(), participants, participantses, GenerateType.normal.ordinal(), new Long(BitComput.pro((long) nextActivity.getStatus(), ActivityFunction.ENABLE_SIGNATURE)).intValue(), client);
                        break;
                    case condition:
                        participantses = new ArrayList<>();
                        participantses.add(participants);
                        doParticipants(instance.getId(), nextActivity.getId(), participantses, handleResult);
                        workitem = generateWorkItem(instance.getId(), nextActivity.getId(), nextActivity.getActivityName(), currentWorkitem.getId(), participants, participantses, GenerateType.normal.ordinal(), new Long(BitComput.pro((long) nextActivity.getStatus(), ActivityFunction.ENABLE_SIGNATURE)).intValue(), client);
                        break;
                    case work:
                        doParticipants(instance.getId(), nextActivity.getId(), participantses, handleResult);
                        break;
                    case freedom:
                        workitem = generateWorkItem(instance.getId(), nextActivity.getId(), nextActivity.getActivityName(), currentWorkitem.getId(), participants, participantses, GenerateType.normal.ordinal(), new Long(BitComput.pro((long) nextActivity.getStatus(), ActivityFunction.ENABLE_SIGNATURE)).intValue(), client);
                        break;
                    case subprocesses:
                        doParticipants(instance.getId(), nextActivity.getId(), participantses, handleResult);
                        break;
                    case end:
                        participantses = new ArrayList<Participants>();
                        participantses.add(participants);
                        doParticipants(instance.getId(), nextActivity.getId(), participantses, handleResult);
                        instance.setRunState(RunState.finished.ordinal());
                        instance.setFinishTime(UTC.date());
                        workitem = generateWorkItem(instance.getId(), nextActivity.getId(), nextActivity.getActivityName(), currentWorkitem.getId(), participants, participantses, GenerateType.normal.ordinal(), new Long(BitComput.pro((long) nextActivity.getStatus(), ActivityFunction.ENABLE_SIGNATURE)).intValue(), client);
                        doWorkitem(workitem, nextActivity, participants, handleResult, handleSuggestion);
                        break;
                    default:
                        workitem = new Workitem();
                        break;
                }
            }

            return workitem.getId();
        } catch (Exception exc) {
            throw new CustomException(exc);
        }
    }

    private List<Participants> doParticipants(Integer instanceId, Integer activityId, List<Participants> participantses, HandleResult handleResult) {
        try {
            switch (handleResult) {
                case adopt:
                    if (participantses.size() == 0) {
                        throw new CustomException("提交到下一步没有审批人");
                    }
                    break;
                case returned:
                    participantses = participantService.findParticipantsByInstanceIdActivityId(instanceId, activityId);
                    break;
            }
            return participantses;
        } catch (Exception exc) {
            throw new CustomException(exc);
        }
    }

    @Transactional
    private boolean doWorkitem(Workitem workitem, Activity currentActivity, Participants participants, HandleResult handleResult, String handleSuggestion) {
        try {
            switch (ActivityType.values()[currentActivity.getActivityType()]) {
                case drafting:
                    return finishWorkitem(workitem, participants, handleResult, handleSuggestion, true);
                case approval:
                    List<WorkitemHandler> handlers = workitem.getWorkitemHandlers().stream().filter(a -> a.getHandleResult().equals(HandleResult.none.ordinal())).collect(Collectors.toList());
                    if (BitComput.pro((long) currentActivity.getStatus(), ActivityFunction.FINISH_TYPE) == FinishType.single.ordinal()) {
                        this.finishWorkitem(workitem, participants, handleResult, handleSuggestion, true);
                        return true;
                    } else if (BitComput.pro((long) currentActivity.getStatus(), ActivityFunction.FINISH_TYPE) == FinishType.percent.ordinal()) {
                        double finishedPercent = 1 - (handlers.size() - 1) / workitem.getWorkitemHandlers().size();
                        if (finishedPercent < (double) currentActivity.getFinishNumber() / 100) {
                            return !this.finishWorkitem(workitem, participants, handleResult, handleSuggestion, false);
                        } else {
                            return this.finishWorkitem(workitem, participants, handleResult, handleSuggestion, true);
                        }
                    } else if (BitComput.pro((long) currentActivity.getStatus(), ActivityFunction.FINISH_TYPE) == FinishType.quantity.ordinal()) {
                        if (workitem.getWorkitemHandlers().size() - handlers.size() + 1 < currentActivity.getFinishNumber()) {
                            return this.finishWorkitem(workitem, participants, handleResult, handleSuggestion, false);
                        } else {
                            return this.finishWorkitem(workitem, participants, handleResult, handleSuggestion, true);
                        }
                    } else if (BitComput.pro((long) currentActivity.getStatus(), ActivityFunction.FINISH_TYPE) == FinishType.all.ordinal()) {
                        if (handlers.size() == 1) {
                            return this.finishWorkitem(workitem, participants, handleResult, handleSuggestion, true);
                        } else {
                            return !this.finishWorkitem(workitem, participants, handleResult, handleSuggestion, false);
                        }
                    }
                    break;
                case work:
                    break;
                case condition:
                    workitem.setHandleResult(handleResult.ordinal());
                    return finishWorkitem(workitem, participants, handleResult, handleSuggestion, true);
                case subprocesses:
                    break;
                case end:
                    workitem.setHandleResult(handleResult.ordinal());
                    return finishWorkitem(workitem, participants, handleResult, handleSuggestion, true);
            }
            return true;
        } catch (Exception exc) {
            throw new CustomException(exc);
        }
    }

    @Transactional
    private boolean finishWorkitem(Workitem workitem, Participants participants, HandleResult handleResult, String handleSuggestion, boolean fnishWorkItem) {
        try {
            if (workitem == null) throw new CustomException("工作项不能为null");
            if (fnishWorkItem) {
                workitem.setHandleResult(handleResult.ordinal());
                workitem.setFinishTime(UTC.date());
            }
            List<WorkitemHandler> workitemHandlers = workitem.getWorkitemHandlers();
            if (workitemHandlers != null && workitemHandlers.size() > 0) {
                for (WorkitemHandler handler : workitemHandlers) {
                    if (handler.getUserId() == participants.getProxyUserId()) {
                        handler.setHandleUserId(participants.getUserId());
                        handler.setHandleUserName(participants.getUserName());
                        handler.setHandleResult(handleResult.ordinal());
                        handler.setHandleSuggestion(handleSuggestion);
                        handler.setFinishTime(UTC.date());
                        if (!fnishWorkItem) break;
                    } else if (fnishWorkItem) {
                        if (handler.getHandleResult() == HandleResult.none.ordinal()) {
                            handler.setHandleResult(HandleResult.cancel.ordinal());
                        }
                    }
                }
            }
            workitem.setInsert(false);
            workitemService.save(workitem);
            for (WorkitemHandler workitemHandler : workitem.getWorkitemHandlers()) {
                workitemHandler.setInsert(false);
            }
            workitemHandlerService.save(workitem.getWorkitemHandlers());
            return true;
        } catch (Exception exc) {
            throw new CustomException(exc);
        }
    }

    @Transactional
    private Workitem generateWorkItem(Integer instanceId, Integer activityId, String activityName, Integer parentId, Participants participants, List<Participants> participantses, Integer generateType, Integer signature, Integer client) {
        try {
            Workitem workitem = new Workitem();
            workitem.setActivityId(activityId);
            workitem.setActivityName(activityName);
            workitem.setInstanceId(instanceId);
            workitem.setParentId(parentId);
            workitem.setStartUserId(participants.getUserId());
            workitem.setStartUserName(participants.getUserName());
            workitem.setGenerateType(generateType);
            workitem.setStartTime(UTC.date());
            workitem.setHandleResult(HandleResult.none.ordinal());
            workitem.setClientType(client);
            workitem.setInsert(true);
            int workitemId = workitemService.save(workitem);
            List<WorkitemHandler> workitemHandlers = generateWorkItemHandler(workitemId, participants, participantses, signature, client);
            workitem.setWorkitemHandlers(workitemHandlers);

            return workitem;
        } catch (Exception exc) {
            throw new CustomException(exc);
        }
    }

    @Transactional
    private List<WorkitemHandler> generateWorkItemHandler(Integer workitemId, Participants participants, List<Participants> participantses, Integer signature, Integer client) {
        try {
            List<WorkitemHandler> workitemHandlers = new ArrayList<WorkitemHandler>();
            for (Participants item : participantses) {
                WorkitemHandler workitemHandler = new WorkitemHandler();
                workitemHandler.setWorkitemId(workitemId);
                workitemHandler.setUserId(item.getUserId());
                workitemHandler.setUserName(item.getUserName());
                workitemHandler.setHandleResult(HandleResult.none.ordinal());
                workitemHandler.setStartTime(UTC.date());
                workitemHandler.setSignature(signature);
                workitemHandler.setClientType(client);
                workitemHandler.setInsert(true);
                workitemHandlers.add(workitemHandler);
            }
            workitemHandlerService.save(workitemHandlers);

            return workitemHandlers;
        } catch (Exception exc) {
            throw new CustomException(exc);
        }
    }

    public boolean retrieve(Integer instanceId, Integer activityId) {
        try {
            List<Workitem> workitems = workitemService.findWorkitemByInstanceId(instanceId);

            return true;
        } catch (Exception exc) {
            throw new CustomException(exc);
        }
    }

    public boolean changeInstanceState(Integer instanceId, RunState runState) {
        try {
            Instance instance = instanceService.findById(instanceId);
            instance.setRunState(runState.ordinal());
            instance.setInsert(false);
            instanceService.save(instance);
            return true;
        } catch (Exception exc) {
            throw new CustomException(exc);
        }
    }

    public boolean delete(Integer instanceId) {
        try {
            Instance instance = instanceService.findById(instanceId);
            instanceService.deleteLogic(instance);
            return true;
        } catch (Exception exc) {
            throw new CustomException(exc);
        }
    }
}
